﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;

namespace WcfContrib.Client.Description
{
    /// <summary>
    /// Represents a collection for description behaviors
    /// </summary>
    /// <typeparam name="T">The type of items</typeparam>
    public class DescriptionCollection<T> : Collection<T>
        where T : class
    {
        bool allowMultipleTypes;
        internal Action<T> ItemAdded { get; set; }
        internal Dictionary<string, bool> addedTypes = new Dictionary<string, bool>();

        /// <summary>
        /// Initializes a new instance of the <see cref="DescriptionCollection&lt;T&gt;"/> class.
        /// </summary>
        public DescriptionCollection() : this(true)
        {

        }

        DescriptionCollection(bool allowMultipleTypes)
        {
            this.allowMultipleTypes = allowMultipleTypes;
        }

        /// <summary>
        /// Replaces the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to replace.</param>
        /// <param name="item">The new value for the element at the specified index. The value can be null for reference types.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// 	<paramref name="index"/> is less than zero.
        /// -or-
        /// <paramref name="index"/> is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"/>.
        /// </exception>
        protected override void SetItem(int index, T item)
        {
            if (OnItemAdd(item))
            {
                base.SetItem(index, item);
            }
        }

        /// <summary>
        /// Inserts an element into the <see cref="T:System.Collections.ObjectModel.Collection`1"/> at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
        /// <param name="item">The object to insert. The value can be null for reference types.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// 	<paramref name="index"/> is less than zero.
        /// -or-
        /// <paramref name="index"/> is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"/>.
        /// </exception>
        protected override void InsertItem(int index, T item)
        {
            if (OnItemAdd(item))
            {
                base.InsertItem(index, item);
            }
        }

        bool OnItemAdd(T item)
        {
            bool shouldAdd = allowMultipleTypes;

            string typeName = item.GetType().FullName;

            if (!addedTypes.ContainsKey(typeName))
            {
                addedTypes.Add(typeName, true);

                shouldAdd = true;
            }

            if (shouldAdd)
            {
                OnItemEvent(ItemAdded, item);
            }

            return shouldAdd;
        }

        void OnItemEvent(Action<T> action, T item)
        {
            if (action != null)
            {
                action(item);
            }
        }

        /// <summary>
        /// Adds the item as unique.
        /// Unique means that if another item of the same type exists in the collection, the item will not be added.
        /// </summary>
        /// <param name="item">The item.</param>
        public void AddUnique(T item)
        {
            string typeName = item.GetType().FullName;
            if (!addedTypes.ContainsKey(typeName))
            {
                Add(item);
            }
        }

        /// <summary>
        /// Adds the range.
        /// Doesn't add as unique.
        /// </summary>
        /// <param name="source">The source.</param>
        public void AddRange(IEnumerable<T> source)
        {
            AddRange(source, false);
        }

        /// <summary>
        /// Adds the items as unique.
        /// Unique means that if another item of the same type exists in the collection, the item will not be added.
        /// </summary>
        /// <param name="source">The items source.</param>
        /// <param name="unique">if set to <c>true</c>, The items are added as unique.</param>
        public void AddRange(IEnumerable<T> source, bool unique)
        {
            foreach (var item in source)
            {
                if (unique)
                {
                    AddUnique(item);
                }
                else
                {
                    Add(item);
                }
            }
        }

        /// <summary>
        /// Peforms an action on all the items.
        /// </summary>
        /// <param name="action">The action.</param>
        public void ForEach(Action<T> action)
        {
            if (Count > 0)
            {
                foreach (var item in this)
                {
                    action(item);
                }
            }
        }

        /// <summary>
        /// Peforms an action on all the items.
        /// </summary>
        /// <param name="action">The action.</param>
        public void ForEach(Action<int, T> action)
        {
            if (Count > 0)
            {
                int index = 0;
                foreach (var item in this)
                {
                    action(index, item);

                    index++;
                }
            }
        }
    }
}
